﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Threading;
using log4net;

namespace VDataAdapter.WorkerManagement
{
    public enum WorkerState
    {
        /// <summary>
        /// Stop is the first state of worker
        /// </summary>
        Stop, 
        /// <summary>
        /// If work started but do not receive the flag yet, it is waiting
        /// </summary>
        Waiting,
        /// <summary>
        /// working so it'll call do work, in case it is hardworking it'll call do work again & again
        /// </summary>
        Working,
        /// <summary>
        /// Working finished job, and will exit
        /// </summary>
        Finished,
        /// <summary>
        /// Do work throw an exception
        /// </summary>
        Error,
        /// <summary>
        /// It is forced to stop even before call do work
        /// </summary>
        ForcedStop
    };

    public abstract class Worker
    {        
        /// <summary>
        /// Declare logger in standard way according to codeproject log4net tutorial
        /// </summary>
        protected readonly ILog _log = LogManager.GetLogger(typeof(Worker));

        protected WorkerManager _manager;

        protected Thread _thread;

        protected ManualResetEvent _waitFlag;

        protected ManualResetEvent _signalFlag;

        protected WorkerState _state = WorkerState.Stop;

        protected Exception _exception;

        protected string _name;

        public Exception WorkException
        {
            get { return _exception; }
        }

        public WorkerState State
        {
            get { return _state; }
        }

        /// <summary>
        /// Cal this function to stop worker in emergency cas
        /// Only work if they haven't gone into working mode yet
        /// </summary>
        public void EmergencyStop()
        {
            _emergencyStop = true;
        }

        private bool _emergencyStop = false;

        /// <summary>
        /// by default, worker is not hard working
        /// using this contructor will give a worker which 
        /// only do work once then stop
        /// </summary>
        /// <param name="waitFlag"></param>
        /// <param name="signalFlag"></param> 
        public Worker(ManualResetEvent waitFlag, ManualResetEvent signalFlag)
        {
            if(waitFlag != null)
                _waitFlag = waitFlag;

            if (signalFlag != null)
                _signalFlag = signalFlag;

            _isHardWorking = false;
        }


        protected string _status = "I'm a good worker";
        /// <summary>
        /// This is a message for worker to say something
        /// if somebody as what are you doing
        /// </summary>
        public string Status
        {
            get { return _status; }
            set { _status = value; }
        }

        private int _hardWorkingMetric = 100;
        /// <summary>
        /// By default, hardworking metric is 100
        /// That means a work (if hardworking)
        /// will call DoWork then sleep for 100 milisecond
        /// </summary>
        public int HardWorkingMetric
        {
            get { return _hardWorkingMetric; }
            set { _hardWorkingMetric = value; }
        }
        
        private bool _isHardWorking;
        /// <summary>
        /// Set worker in Hardworking mode so that it can stop
        /// during its iteration
        /// </summary>
        public bool IsHardWorking
        {
            get { return _isHardWorking; }
            set { _isHardWorking = value; }
        }

        public Worker(ManualResetEvent waitFlag, ManualResetEvent signalFlag, bool isHardWorking)
        {
            if (waitFlag != null)
                _waitFlag = waitFlag;

            if (signalFlag != null)
                _signalFlag = signalFlag;

            _isHardWorking = isHardWorking;
        }

        public void AcceptManager(WorkerManager manager)
        {
            _manager = manager;
            _manager.Report(this); // register him self with manager
        }

        public void StartWork(Object data)
        {
            _thread = new Thread(StartWorkingThread);
            if (_name != null)
                _thread.Name = _name;

            _thread.IsBackground = true; // to make it does not get kill
            _thread.Start(data);            
        }

        protected void StartWorkingThread(Object data)
        {
            _log.Info(_name + " started and wait for flag");
            // wait for others' flag
            _state = WorkerState.Waiting;

            WaitForFlags();

            // stop working immediately
            if (_emergencyStop)
            {
                _state = WorkerState.ForcedStop;
                return;
            }

            // report start working
            _state = WorkerState.Working;

            // take over my flag
            ResetMyFlags();

            _log.Info(_name + " working");

            try
            {
                // do work once
                DoWork(data); 

                // then if i'm hardworking, I'll do more
                while (_isHardWorking)
                {                    
                    Thread.Sleep(_hardWorkingMetric);
                    DoWork(data); 
                }
            }
            catch (Exception ex)
            {
                _state = WorkerState.Error;
                _exception = ex;

                if (_manager != null)
                    _manager.ReportException(this, ex);                
            }

            // i'm finished only if there was no error occur
            if(_state != WorkerState.Error)
                _state = WorkerState.Finished;

            _log.Info(_name + " finished");
            // signal other waiting threads
            SignalMyFlags();

            
        }

        private void SignalMyFlags()
        {
            if(_signalFlag != null)                
                _signalFlag.Set();
        }

        private void ResetMyFlags()
        {
            if (_signalFlag != null)
                _signalFlag.Reset();
        }

        private void WaitForFlags()
        {
            if (_waitFlag != null)
                _waitFlag.WaitOne();
        }

        public void WaitToStop()
        {
            if (_thread != null && _thread.IsAlive)
            {
                _thread.Join();
                
            }
        }

        public abstract void StopWork();


        protected abstract void DoWork(Object data);
    }

    /// <summary>
    /// This class do nothing but wait for some seconds
    /// </summary>
    class WaitingWorker : Worker
    {
        private bool _stop;

        public WaitingWorker(string parentName,ManualResetEvent wait, ManualResetEvent signal, int seconds)
            : base(wait, signal) 
        {
            _name = "WaitingWorker";
            _seconds = seconds;
            _stop = false;
        }

        public WaitingWorker(ManualResetEvent wait, ManualResetEvent signal, int seconds, bool isHardWorking)
            : base(wait, signal, isHardWorking)
        {
            _seconds = seconds;
            
        }

        protected int _seconds;
        
        protected override void DoWork(object data)
        {            
            for (int i = 0; i < _seconds; i++)
            {
                if (_stop)
                {
                    return;
                }
                Thread.Sleep(1000);
            }            
        }

        override
        public void StopWork()
        {
            _stop = true;
        }
    }
        
}
